﻿//-----------------------------------------------------------------------
// <copyright file="Agenda.cs" company="tuliprules.org">
//     Copyright (c) 2010 tuplelister, tuliprules.org.
// </copyright>
// <summary>This is the Agenda class.</summary>
//-----------------------------------------------------------------------

namespace Tulip.Engine.ConflictResolution
{
    using System.Collections.Generic;
    using Tuple = Tuples.Tuple;

    /// <summary>
    /// The agenda for the production system.
    /// </summary>
    /// <remarks>
    /// <para>The agenda is used during conflict resolution.   NB. sequential rule processing
    /// may not expolit conflict resolutuion, and hence the Agenda may not be used in
    /// all circumastances.</para>
    /// <para>The agenda maintains a sorted list of priorities.   Each priority is a list to
    /// which the engine can add productions.   The agenda adds productions to the 
    /// approprite priority by inspecting the Salience property of the production tuple.</para>
    /// </remarks>
    public class Agenda
    {
        /// <summary>
        /// Sorted list of priorities.   Each priority is an agenda list.
        /// </summary>
        private readonly SortedList<int, IAgendaList> priorities;

        /// <summary>
        /// The confluict resolution strategy currently being used.
        /// </summary>
        private readonly ConflictResolutionStrategy conflictResStategy;

        /// <summary>
        /// Initializes a new instance of the Agenda class.
        /// </summary>
        /// <param name="conflictResStrategy">The current conflict resolution strategy.</param>
        public Agenda(ConflictResolutionStrategy conflictResStrategy)
        {
            this.priorities = new SortedList<int, IAgendaList>(16, new ReverseCompare());
            this.conflictResStategy = conflictResStrategy;
        }

        /// <summary>
        /// Gets the count of all productions curently on the agenda.
        /// </summary>
        public int Count
        {
            get
            {
                var count = 0;

                for (var idx = 0; idx < this.priorities.Values.Count; idx++)
                {
                    count += this.priorities.Values[idx].Count;
                }

                return count;
            }
            //////set
            //////{
            //////    ;
            //////}
        }

        /// <summary>
        /// Registers a priority class with the agenda.
        /// </summary>
        /// <param name="priority">The slaiance value associated with the registered priority class.</param>
        /// <returns>The agenda list for the registered priority class.</returns>
        public IAgendaList RegisterPriorityClass(int priority)
        {
            IAgendaList agendaList;

            if (this.priorities.ContainsKey(priority))
            {
                agendaList = this.priorities[priority];
            }
            else
            {
                agendaList = this.conflictResStategy.CreateAgendaList();
                this.priorities.Add(priority, agendaList);
            }

            return agendaList;
        }

        /// <summary>
        /// Adds a production tuple to the agenda, inserting it into the correct priority.
        /// </summary>
        /// <param name="production">The production tuple to be added to the agenda.</param>
        public void Push(Tuple production)
        {
            this.priorities[production.Salience].Push(production);
        }

        /// <summary>
        /// Removes and returns the next production tuple from the agenda.
        /// </summary>
        /// <returns>The production tuple at the head of the agenda.</returns>
        public Tuple Pop()
        {
            for (var idx = 0; idx < this.priorities.Values.Count; idx++)
            {
                if (this.priorities.Values[idx].Count > 0)
                {
                    return this.priorities.Values[idx].Pop();
                }
            }

            return null;
        }

        /// <summary>
        /// Peeks at the next production tuple that will be returned when 'Pop' is called.
        /// </summary>
        /// <returns>The production tuple at the head of the agenda.</returns>
        public Tuple Peek()
        {
            for (var idx = 0; idx < this.priorities.Values.Count; idx++)
            {
                if (this.priorities.Values[idx].Count > 0)
                {
                    return this.priorities.Values[idx].Peek();
                }
            }

            return null;
        }

        /// <summary>
        /// Removes a production tuple from the agenda.
        /// </summary>
        /// <param name="production">The production tuple to be removed from the agenda.</param>
        public void Remove(Tuple production)
        {
            if (production.AgendaTimestamp >= 0)
            {
                this.priorities[production.Salience].Remove(production);
            }
        }

        /// <summary>
        /// Reverse comparer.   Used to sort priorities in reverse order (from highest to
        /// lowest salience).
        /// </summary>
        private class ReverseCompare : IComparer<int>
        {
            #region IComparer<int> Members

            /// <summary>
            /// Compares two saliances.
            /// </summary>
            /// <param name="salianceX">First saliance vlaue.</param>
            /// <param name="salianceY">Second saliance vlaue.</param>
            /// <returns>0, if saliances are equal, or non zero.</returns>
            public int Compare(int salianceX, int salianceY)
            {
                if (salianceX == salianceY)
                {
                    return 0;
                }

                if (salianceX > salianceY)
                {
                    return -1;
                }

                return 1;
            }

            #endregion
        }
    }
}
